import 'package:dio/dio.dart';
import 'package:flutter_module/common/http/ApiConstant.dart';
import 'package:flutter_module/common/store/shared_preferences.dart';
import 'ApiResponse.dart';
import 'exception/AppException.dart';
import 'exception/AppExceptionHandle.dart';
import 'exception/ExceptionConstant.dart';
import 'interceptor/YZErrorInterceptor.dart';
import 'interceptor/YZRequestInterceptor.dart';
import 'proxy/YouthProxy.dart';

const CACHE_ENABLE = false;
const KEY_BASE_URL = "BASE_URL";
const KEY_HEADER = "BASE_HEADER";
const KEY_ISDEBUG = "isDebug";
const BASE_URL = "https://sjqn-geteway.shangjinqingnian.com";
const int CONNECT_TIMEOUT = 10000;
const int RECEIVE_TIMEOUT = 10000;
const int PAGE_SIZE = 20;

typedef HttpSuccessCallBack = void Function(dynamic data, int total);
typedef HttpErrorCallBack = void Function(Exception e);

class YZHttp {
  static const commonHeaders = {"platform": "flutter"};

  static final YZHttp _instance = YZHttp._internal();
  // 单例模式使用Http类，
  factory YZHttp() => _instance;

  static late final Dio dio;
  CancelToken _cancelToken = new CancelToken();

  YZHttp._internal() {
    // BaseOptions、Options、RequestOptions 都可以配置参数，优先级别依次递增，且可以根据优先级别覆盖参数
    BaseOptions options = BaseOptions();

    dio = Dio(options);
    // 由于拦截器队列的执行顺序是FIFO，如果把log拦截器添加到了最前面，
    // 则后面拦截器对options的更改就不会被打印（但依然会生效）， 所以建议把log拦截添加到队尾。

    //debug下添加代理adapter
    YouthProxy.setProxy(dio);

    // 请求拦截器
    dio.interceptors.add(YZRequestInterceptor());
    // 错误拦截器
    dio.interceptors.add(YZErrorInterceptor());
    // log拦截器
    dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: true));
  }

  ///初始化公共属性
  ///
  /// [baseUrl] 地址前缀
  /// [connectTimeout] 连接超时赶时间
  /// [receiveTimeout] 接收超时赶时间
  /// [interceptors] 基础拦截器
  void init({
    String? baseUrl,
    int connectTimeout = CONNECT_TIMEOUT,
    int receiveTimeout = RECEIVE_TIMEOUT,
    Map<String, String>? headers,
    List<Interceptor>? interceptors,
  }) {
    dio.options = dio.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: Duration(milliseconds: connectTimeout),
      receiveTimeout: Duration(milliseconds: receiveTimeout),
      headers: headers ?? commonHeaders,
    );
    // 在初始化http类的时候，可以传入拦截器
    if (interceptors != null && interceptors.isNotEmpty) {
      dio.interceptors.addAll(interceptors);
    }
  }

  // 关闭dio
  void cancelRequests({required CancelToken token}) {
    _cancelToken.cancel("cancelled");
  }

  // 添加认证
  // 读取本地配置
  Map<String, dynamic>? getAuthorizationHeader() {
    Map<String, dynamic> headers = {};
    // 从getx或者sputils中获取
    // String accessToken = Global.accessToken;
    // String accessToken = "";
    // if (accessToken != null) {
    //   headers = {
    //     'Authorization': 'Bearer $accessToken',
    //   };
    // }
    headers = SharedPreferences.instance.httpHeader.toJson();
    print('bind----http_header----${headers.toString()}');
    return headers;
  }

  String getCommonPath(String path) {
    return '$api_uri_path_prefix$path';
  }

  void get(String path,
      {Map<String, dynamic>? params,
      Options? options,
      CancelToken? cancelToken,
      bool refresh = false,
      bool noCache = !CACHE_ENABLE,
      String? cacheKey,
      bool cacheDisk = false,
      required HttpSuccessCallBack successCallBack,
      required HttpErrorCallBack errorCallBack}) async {
    Options requestOptions = options ?? Options();
    requestOptions = requestOptions.copyWith(
      extra: {
        "refresh": refresh,
        "noCache": noCache,
        "cacheKey": cacheKey,
        "cacheDisk": cacheDisk,
      },
    );
    Map<String, dynamic>? _authorization = getAuthorizationHeader();
    if (_authorization != null) {
      requestOptions = requestOptions.copyWith(headers: _authorization);
    }
    try {
      var response = await dio.get(
        getCommonPath(path),
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken,
      );
      responseFactory(response,
          successCallBack: successCallBack, errorCallBack: errorCallBack);
    } on Exception catch (e) {
      errorFactory(e, errorCallBack: errorCallBack);
    }
  }

  void post(String path,
      {Map<String, dynamic>? params,
      data,
      Options? options,
      CancelToken? cancelToken,
      required HttpSuccessCallBack successCallBack,
      required HttpErrorCallBack errorCallBack}) async {
    Options requestOptions = options ?? Options();
    Map<String, dynamic>? _authorization = getAuthorizationHeader();
    if (_authorization != null) {
      requestOptions = requestOptions.copyWith(headers: _authorization);
    }
    try {
      var response = await dio.post(
        getCommonPath(path),
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken,
      );
      responseFactory(response,
          successCallBack: successCallBack, errorCallBack: errorCallBack);
    } on Exception catch (e) {
      errorFactory(e, errorCallBack: errorCallBack);
    }
  }

  void upload(String path, FormData data,
      {Map<String, dynamic>? params,
      Options? options,
      CancelToken? cancelToken,
      required void Function(int, int)? onSendProgress,
      required HttpSuccessCallBack successCallBack,
      required HttpErrorCallBack errorCallBack}) async {
    Options requestOptions = options ?? Options();
    Map<String, dynamic>? _authorization = getAuthorizationHeader();
    if (_authorization != null) {
      requestOptions = requestOptions.copyWith(headers: _authorization);
    }
    try {
      var response = await dio.post(
        getCommonPath(path),
        data: data,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken,
        onSendProgress: onSendProgress,
      );
      responseFactory(response,
          successCallBack: successCallBack, errorCallBack: errorCallBack);
    } on Exception catch (e) {
      errorFactory(e, errorCallBack: errorCallBack);
    }
  }

  void uploadStream(String path, List<int> postData,
      {Map<String, dynamic>? params,
      Options? options,
      CancelToken? cancelToken,
      required void Function(int, int)? onSendProgress,
      required HttpSuccessCallBack successCallBack,
      required HttpErrorCallBack errorCallBack}) async {
    Options requestOptions = options ??
        Options(
          headers: {
            Headers.contentLengthHeader: postData.length, // 设置content-length
          },
        );
    Map<String, dynamic>? _authorization = getAuthorizationHeader();
    if (_authorization != null) {
      requestOptions = requestOptions.copyWith(headers: _authorization);
    }
    try {
      var response = await dio.post(
        getCommonPath(path),
        data: Stream.fromIterable(
            postData.map((e) => [e])), //创建一个Stream<List<int>>,
        queryParameters: params,
        options: requestOptions,
        cancelToken: cancelToken ?? _cancelToken,
        onSendProgress: onSendProgress,
      );
      responseFactory(response,
          successCallBack: successCallBack, errorCallBack: errorCallBack);
    } on Exception catch (e) {
      errorFactory(e, errorCallBack: errorCallBack);
    }
  }

  void responseFactory(Response<dynamic> response,
      {required Function successCallBack, required Function errorCallBack}) {
    ApiResponse apiResponse = ApiResponse.fromJson(response.data);
    // 业务code判断
    if (apiResponse.code == 200) {
      if (apiResponse.data != null) {
        successCallBack(apiResponse.data, apiResponse.total ?? 0);
      } else {
        errorCallBack(AppException(NODATA_ERROR_CODE, ""));
      }
    } else {
      errorCallBack(AppExceptionHandle()
          .handelError(response: response, api: apiResponse));
    }
  }

  void errorFactory(Exception exception, {required Function errorCallBack}) {
    errorCallBack(AppException.create(exception as DioException));
  }
}
